package geez.widget;

import com.google.gwt.event.dom.client.*;
import com.google.gwt.user.client.ui.*;

import java.util.ArrayList;
import java.util.List;

import static geez.widget.Geez.withStyle;

public class GSuggestBox<T extends GSuggestBox> extends Composite implements GFocusable, GClickable, GChangable {
    GTextBox textBox;
    private PopupPanel popup;
    private GSuggestionRenderer renderer;
    private SuggestOracle oracle;
    private GFlowPanel popupContent;
    private SuggestionWidgetWrapper currentSelection = new SuggestionWidgetWrapper(new Label("No selection"), -1);
    private List<? extends SuggestOracle.Suggestion> currentSuggestions;
    private String selectedCss = "selected";

    public GSuggestBox(SuggestOracle oracle) {
        this(oracle, new GTextBox());
    }

    GSuggestBox(SuggestOracle oracle, GTextBox textBox) {
        this.textBox = textBox;
        textBox.addFocusHandler(new FocusHandler() {
            public void onFocus(FocusEvent focusEvent) {
                updateSuggestionContent();
            }
        });
        textBox.addBlurHandler(new BlurHandler() {
            public void onBlur(BlurEvent blurEvent) {
                popup.hide();
            }
        });
        textBox.addKeyDownHandler(new KeyDownHandler() {
            public void onKeyDown(KeyDownEvent event) {
                if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER || event.getNativeKeyCode() == KeyCodes.KEY_TAB) {
                    chooseSelection(currentSelection);
                }
            }
        });
        textBox.addKeyUpHandler(new KeyUpHandler() {

            public void onKeyUp(KeyUpEvent event) {
                if (event.isUpArrow()) {
                    moveSelectionUp();
                } else if (event.isDownArrow()) {
                    moveSelectionDown();
                } else {
                    updateSuggestionContent();
                }

            }
        });
        popup = new PopupPanel(true);
        renderer = new GDefaultSuggestionRenderer();
        this.oracle = oracle;
        popupContent = new GFlowPanel();
        popup.add(popupContent);
        initWidget(textBox);
    }

    private void chooseSelection(Widget widget) {
        int selectedIndex = popupContent.getWidgetIndex(widget);
        SuggestOracle.Suggestion suggestion = currentSuggestions.get(selectedIndex);
        textBox.setText(suggestion.getReplacementString());
    }

    private void moveSelectionDown() {
        int currentSelectedIndex = getCurrentlySelectedWidgetIndex();
        if (currentSelectedIndex < popupContent.getWidgetCount() - 1) {
            selectWidgetWithIndex(++currentSelectedIndex);
        }
    }

    private void moveSelectionUp() {
        int currentSelectedIndex = getCurrentlySelectedWidgetIndex();
        if (currentSelectedIndex > 0) {
            selectWidgetWithIndex(--currentSelectedIndex);
        }
    }

    private void selectWidgetWithIndex(int index) {
        currentSelection.unselect();
        SuggestionWidgetWrapper newlySelectedWidget = getSelectionWidget(index);
        newlySelectedWidget.select();
        currentSelection = newlySelectedWidget;
    }

    private SuggestionWidgetWrapper getSelectionWidget(int index) {
        return (SuggestionWidgetWrapper) popupContent.getWidget(index);
    }

    private int getCurrentlySelectedWidgetIndex() {
        return popupContent.getWidgetIndex(currentSelection);
    }

    private void updateSuggestionContent() {
        popupContent.clear();
        oracle.requestSuggestions(new SuggestOracle.Request(textBox.getText()), new RenderSuggestionsCallback());
        if (currentSuggestions.size() > 0) {
            popup.setPopupPosition(textBox.getAbsoluteLeft(), textBox.getAbsoluteTop() + textBox.getOffsetHeight());
            popup.show();
        } else {
            popup.hide();
        }
    }

    private Widget clickToChoose(Widget widget, int index) {
        return new SuggestionWidgetWrapper(widget, index);
    }

    public T id(String id) {
        Geez.withNameAndId(this, id);
        Geez.withNameAndId(popup, id + ".popup");
        return typeCastThis();
    }

    public T css(String cssClass, String... optionalClasses) {
        withStyle(this, cssClass, optionalClasses);
        return typeCastThis();
    }

    public GSuggestBox show() {
        setVisible(true);
        return this;
    }

    public GSuggestBox hide() {
        setVisible(false);
        return this;
    }

    public T popupCss(String css, String... optionalClasses) {
        Geez.withStyle(popup, css, optionalClasses);
        return typeCastThis();
    }

    public T withRenderer(GSuggestionRenderer renderer) {
        this.renderer = renderer;
        return typeCastThis();
    }

    private T typeCastThis() {
        return (T) this;
    }

    public GSuggestBox add(ClickHandler handler) {
        textBox.addClickHandler(handler);
        return this;
    }

    public GSuggestBox add(ChangeHandler handler) {
        textBox.addChangeHandler(handler);
        return this;
    }

    public GSuggestBox add(FocusHandler handler) {
        textBox.addFocusHandler(handler);
        return this;
    }

    public GSuggestBox add(BlurHandler handler) {
        textBox.addBlurHandler(handler);
        return this;
    }

    public GSuggestBox enable() {
        textBox.setEnabled(true);
        return this;
    }

    public GSuggestBox disable() {
        textBox.setEnabled(false);
        return this;
    }

    public GSuggestBox focus() {
        textBox.setFocus(true);
        return this;
    }

    private class SuggestionWidgetWrapper extends GFocusPanel<SuggestionWidgetWrapper> {
        private String originalStyle = "none";

        private SuggestionWidgetWrapper(Widget child, final int index) {
            super(child);
            addMouseDownHandler(new WidgetAwareMouseDownHandler(child));
            addMouseOverHandler(new MouseOverHandler() {
                public void onMouseOver(MouseOverEvent mouseOverEvent) {
                    selectWidgetWithIndex(index);
                }
            });
        }

        public void select() {
            originalStyle = getWidget().getStylePrimaryName();
            getWidget().setStylePrimaryName(selectedCss);
        }

        public void unselect() {
            getWidget().setStylePrimaryName(originalStyle);
        }

        private class WidgetAwareMouseDownHandler implements MouseDownHandler {
            private Widget child;

            public WidgetAwareMouseDownHandler(Widget child) {
                this.child = child;
            }

            public void onMouseDown(MouseDownEvent event) {
                if (child.equals(event.getSource()))
                    chooseSelection(child);
            }
        }
    }

    private class RenderSuggestionsCallback implements SuggestOracle.Callback {
        public void onSuggestionsReady(SuggestOracle.Request request, SuggestOracle.Response response) {
            currentSuggestions = new ArrayList<SuggestOracle.Suggestion>(response.getSuggestions());
            for (int index = 0; index < currentSuggestions.size(); index++) {
                Widget renderedSelection = clickToChoose(
                        renderer.render(currentSuggestions.get(index), index),
                        index
                );
                popupContent.append(renderedSelection);
            }
            if (!currentSuggestions.isEmpty()) {
                selectWidgetWithIndex(0);
            }
        }
    }
}
